﻿using System.Diagnostics;
using System.Diagnostics.Contracts;
using MathService;
using MathService.Vectors;

namespace System
{
    using DST = DebuggerStepThroughAttribute;

    /// <summary>Расширения для чисел двойной точности</summary>
    public static class DoubleExtentions
    {
        /// <summary>Квадратный корень</summary>
        /// <param name="x">Число из которого извлекается квадратный корень</param>
        /// <returns>Квадратный корень числа</returns>
        [DST, Pure]
        public static double Sqrt(this double x) { return Math.Sqrt(x); }

        /// <summary>Является ли число целым?</summary>
        /// <param name="x">Проверяемое число</param>
        /// <returns>Истина, если число целое</returns>
        [DST, Pure]
        public static bool IsInt(this double x) { return (((int)x) - x).Equals(0); }

        /// <summary>Округление числа до указанного количества знаков после запятой </summary>
        /// <param name="x">Округляемое число</param>
        /// <param name="n">Количество знаков после запятой при n >= 0 и до запятой при n меньше 0</param>
        /// <returns>Число, округлённое до указанной точности</returns>
        [DST, Pure]
        public static double Round(this double x, int n = 0)
        {
            Contract.Requires(n <= 15);

            if(n >= 0)
                return Math.Round(x, n);

            var nn = -n;
            var b = Math.Pow(10, nn);
            return Math.Round(x / b) * b;
        }

        public static Vector3D RoundAdaptive(this Vector3D r, int n = 1)
        {
            return new Vector3D(r.X.RoundAdaptive(n), r.Y.RoundAdaptive(n), r.Z.RoundAdaptive(n));
        }


        /// <summary>Адаптивное округление</summary>
        /// <param name="x">Округляемая величина</param>
        /// <param name="n">Количество значащих разрядов</param>
        /// <returns>Число с указанным количеством значащих разрядов</returns>
        //[DST, Pure]
        public static double RoundAdaptive(this double x, int n = 1)
        {
            Contract.Requires(n > 0);
            if(x.Equals(0)) return 0;
            if(double.IsNaN(x)) return x;
            if(double.IsInfinity(x)) return x;
            var sign = Math.Sign(x);
            x = x.GetAbs();
            var b = Math.Pow(10, ((int)Math.Log10(x)) - 1);
            return Math.Round(x / b, n) * b * sign;
        }

        /// <summary>Получить обратное число</summary>
        /// <param name="x">Инвертируемое число</param>
        /// <returns>Число, обратное к исходном</returns>
        [DST, Pure]
        public static double GetInverse(this double x)
        {
            Contract.Requires(!x.Equals(0));
            Contract.EnsuresOnThrow<DivideByZeroException>(x.Equals(0));
            return 1 / x;
        }

        /// <summary>Число по модулю</summary>
        /// <param name="x">Исходное число</param>
        /// <param name="mod">Модуль</param>
        /// <returns>Число по модулю</returns>
        [DST, Pure]
        public static double GetAbsMod(this double x, double mod)
        {
            Contract.Requires(!mod.Equals(0));
            return x % mod + (x < 0 ? mod : 0);
        }

        /// <summary>Модуль числа</summary>
        /// <param name="x">Действительное вещественное число</param>
        /// <returns>Модуль числа</returns>
        [DST, Pure]
        public static double GetAbs(this double x)
        {
            Contract.Ensures(Contract.Result<double>() >= 0);
            return Math.Abs(x);
        }

        /// <summary>Возведение в целую степень</summary>
        /// <param name="x">Действительное число</param><param name="n">Целочисленный показатель степени</param>
        /// <returns>x^n</returns>
        [DST, Pure]
        public static double Power(this double x, int n)
        {
            Contract.Requires(!x.Equals(0));
            if(n > 1000 || n < -1000) return Math.Pow(x, n);
            if(n < 0) return (1 / x).Power(-n);
            var result = 1.0;
            for(var i = 0; i < n; i++)
                result *= x;
            return result;
        }

        /// <summary>Возведение числа в действительную степень</summary>
        /// <param name="x">ОСнование</param><param name="y">Действительный показатель степени</param>
        /// <returns>Действительное число x возведённое в степень y: x^y</returns>
        [DST, Pure]
        public static double Power(this double x, double y) { return Math.Pow(x, y); }

        /// <summary>Возведение числа в комплексную степень</summary>
        /// <param name="x">Основание</param><param name="z">Комплексный показатель степень</param>
        /// <returns>Значение x^z, где x - действительное, z - комплексное</returns>
        [DST, Pure]
        public static Complex Power(this double x, Complex z) { return x ^ z; }

        /// <summary>Преобразование в децебеллы по амплитуде</summary>
        /// <param name="x">Амплитудное значение 20*lg(x)</param>
        /// <returns>Значение в децебеллах</returns>
        [DST, Pure]
        public static double In_dB(this double x) { return 20 * Math.Log10(x); }

        /// <summary>Преобразование в децебеллы по мощности</summary>
        /// <param name="x">Значение мощности 10*lg(x)</param>
        /// <returns>Значение в децебеллах</returns>
        [DST, Pure]
        public static double In_dB_byPower(this double x) { return 10 * Math.Log10(x); }

        /// <summary>Преобразование из децебеллов в разы по значению (амплитуде)</summary>
        /// <param name="db">Значение в децебеллах 10^(x/20)</param>
        /// <returns>Значение в разах по амплитуде</returns>
        [DST, Pure]
        public static double From_dB(this double db) { return Math.Pow(10, db / 20); }

        /// <summary>Преобразование из децебеллов в разы по мощности</summary>
        /// <param name="db">Значение в децебеллах 10^(x/10)</param>
        /// <returns>Значение в разах по мощности</returns>
        [DST, Pure]
        public static double From_dB_byPower(this double db) { return Math.Pow(10, db / 10); }

        /// <summary>Преобразование значения в радианы</summary>
        /// <param name="deg">Значение в градусах</param><returns>Значение в радианах</returns>
        [DST, Pure]
        public static double ToRad(this double deg) { return deg * Consts.Geometry.ToRad; }

        /// <summary>Преобразование значения в градусы</summary>
        /// <param name="rad">Значение в радианах</param><returns>Значение в градусах</returns>
        [DST, Pure]
        public static double ToDeg(this double rad) { return rad * Consts.Geometry.ToDeg; }
    }
}
